// -*- mode: ObjC -*-

//  This file is part of class-dump, a utility for examining the Objective-C segment of Mach-O files.
//  Copyright (C) 1997-2019 Steve Nygard.

#import "CDClassDump.h"

//#import "CDFatArch.h"
//#import "CDFatFile.h"
//#import "CDLCDylib.h"
//#import "CDMachOFile.h"
//#import "CDObjectiveCProcessor.h"
#import "CDType.h"
#import "CDTypeFormatter.h"
#import "CDTypeParser.h"
#import "CDVisitor.h"
//#import "CDLCSegment.h"
#import "CDTypeController.h"
//#import "CDSearchPathState.h"
#import "CDObjectiveCProcessor.h"

NSString *CDErrorDomain_ClassDump = @"CDErrorDomain_ClassDump";

NSString *CDErrorKey_Exception    = @"CDErrorKey_Exception";

@interface CDClassDump ()
@end

#pragma mark -

@implementation CDClassDump
{
//    CDSearchPathState *_searchPathState;
    
    BOOL _shouldProcessRecursively;
    BOOL _shouldSortClasses; // And categories, protocols
    BOOL _shouldSortClassesByInheritance; // And categories, protocols
    BOOL _shouldSortMethods;
    
    BOOL _shouldShowIvarOffsets;
    BOOL _shouldShowMethodAddresses;
    BOOL _shouldShowHeader;
    
    NSRegularExpression *_regularExpression;
    
    NSString *_sdkRoot;
    NSMutableArray *_machOFiles;
    NSMutableDictionary *_machOFilesByName;
    NSMutableArray *_objcProcessors;
    
    CDTypeController *_typeController;
    
//    CDArch _targetArch;
}

- (id)init;
{
    if ((self = [super init])) {
//        _searchPathState = [[CDSearchPathState alloc] init];
        _sdkRoot = nil;
        
        _machOFiles = [[NSMutableArray alloc] init];
        _machOFilesByName = [[NSMutableDictionary alloc] init];
        _objcProcessors = [[NSMutableArray alloc] init];
        
        _typeController = [[CDTypeController alloc] initWithClassDump:self];
        
        // These can be ppc, ppc7400, ppc64, i386, x86_64
//        _targetArch.cputype = CPU_TYPE_ANY;
//        _targetArch.cpusubtype = 0;
        
        _shouldShowHeader = YES;
    }

    return self;
}

#pragma mark - Regular expression handling

- (BOOL)shouldShowName:(NSString *)name;
{
    if (self.regularExpression != nil) {
        NSTextCheckingResult *firstMatch = [self.regularExpression firstMatchInString:name options:(NSMatchingOptions)0 range:NSMakeRange(0, [name length])];
        return firstMatch != nil;
    }

    return YES;
}

#pragma mark -

- (BOOL)containsObjectiveCData;
{
//    for (CDObjectiveCProcessor *processor in self.objcProcessors) {
//        if ([processor hasObjectiveCData])
//            return YES;
//    }

    return NO;
}

- (BOOL)hasEncryptedFiles;
{
//    for (CDMachOFile *machOFile in self.machOFiles) {
//        if ([machOFile isEncrypted]) {
//            return YES;
//        }
//    }

    return NO;
}

- (BOOL)hasObjectiveCRuntimeInfo;
{
    return self.containsObjectiveCData || self.hasEncryptedFiles;
}

//- (BOOL)loadFile:(CDFile *)file error:(NSError *__autoreleasing *)error;
//{
//    //NSLog(@"targetArch: (%08x, %08x)", targetArch.cputype, targetArch.cpusubtype);
//    CDMachOFile *machOFile = [file machOFileWithArch:_targetArch];
//    //NSLog(@"machOFile: %@", machOFile);
//    if (machOFile == nil) {
//        if (error != NULL) {
//            NSString *failureReason;
//            NSString *targetArchName = CDNameForCPUType(_targetArch.cputype, _targetArch.cpusubtype);
//            if ([file isKindOfClass:[CDFatFile class]] && [(CDFatFile *)file containsArchitecture:_targetArch]) {
//                failureReason = [NSString stringWithFormat:@"Fat file doesn't contain a valid Mach-O file for the specified architecture (%@).  "
//                                                            "It probably means that class-dump was run on a static library, which is not supported.", targetArchName];
//            } else {
//                failureReason = [NSString stringWithFormat:@"File doesn't contain the specified architecture (%@).  Available architectures are %@.", targetArchName, file.architectureNameDescription];
//            }
//            NSDictionary *userInfo = @{ NSLocalizedFailureReasonErrorKey : failureReason };
//            *error = [NSError errorWithDomain:CDErrorDomain_ClassDump code:0 userInfo:userInfo];
//        }
//        return NO;
//    }
//
//    // Set before processing recursively.  This was getting caught on CoreUI on 10.6
//    assert([machOFile filename] != nil);
//    [_machOFiles addObject:machOFile];
//    _machOFilesByName[machOFile.filename] = machOFile;
//
//    if ([self shouldProcessRecursively]) {
//        @try {
//            for (CDLoadCommand *loadCommand in [machOFile loadCommands]) {
//                if ([loadCommand isKindOfClass:[CDLCDylib class]]) {
//                    CDLCDylib *dylibCommand = (CDLCDylib *)loadCommand;
//                    if ([dylibCommand cmd] == LC_LOAD_DYLIB) {
//                        [self.searchPathState pushSearchPaths:[machOFile runPaths]];
//                        {
//                            NSString *loaderPathPrefix = @"@loader_path";
//                            
//                            NSString *path = [dylibCommand path];
//                            if ([path hasPrefix:loaderPathPrefix]) {
//                                NSString *loaderPath = [machOFile.filename stringByDeletingLastPathComponent];
//                                path = [[path stringByReplacingOccurrencesOfString:loaderPathPrefix withString:loaderPath] stringByStandardizingPath];
//                            }
//                            [self machOFileWithName:path]; // Loads as a side effect
//                        }
//                        [self.searchPathState popSearchPaths];
//                    }
//                }
//            }
//        }
//        @catch (NSException *exception) {
//            NSLog(@"Caught exception: %@", exception);
//            if (error != NULL) {
//                NSDictionary *userInfo = @{
//                NSLocalizedFailureReasonErrorKey : @"Caught exception",
//                CDErrorKey_Exception             : exception,
//                };
//                *error = [NSError errorWithDomain:CDErrorDomain_ClassDump code:0 userInfo:userInfo];
//            }
//            return NO;
//        }
//    }
//
//    return YES;
//}

#pragma mark -

- (void)processObjectiveCData;
{
//    for (CDMachOFile *machOFile in self.machOFiles) {
//        CDObjectiveCProcessor *processor = [[[machOFile processorClass] alloc] initWithMachOFile:machOFile];
//        [processor process];
//        [_objcProcessors addObject:processor];
//    }
}

// This visits everything segment processors, classes, categories.  It skips over modules.  Need something to visit modules so we can generate separate headers.
- (void)recursivelyVisit:(CDVisitor *)visitor;
{
    [visitor willBeginVisiting];

    for (CDObjectiveCProcessor *processor in self.objcProcessors) {
        [processor recursivelyVisit:visitor];
    }

    [visitor didEndVisiting];
}

//- (CDMachOFile *)machOFileWithName:(NSString *)name;
//{
//    NSString *adjustedName = nil;
//    NSString *executablePathPrefix = @"@executable_path";
//    NSString *rpathPrefix = @"@rpath";
//
//    if ([name hasPrefix:executablePathPrefix]) {
//        adjustedName = [name stringByReplacingOccurrencesOfString:executablePathPrefix withString:self.searchPathState.executablePath];
//    } else if ([name hasPrefix:rpathPrefix]) {
//        //NSLog(@"Searching for %@ through run paths: %@", name, [searchPathState searchPaths]);
//        for (NSString *searchPath in [self.searchPathState searchPaths]) {
//            NSString *str = [name stringByReplacingOccurrencesOfString:rpathPrefix withString:searchPath];
//            //NSLog(@"trying %@", str);
//            if ([[NSFileManager defaultManager] fileExistsAtPath:str]) {
//                adjustedName = str;
//                //NSLog(@"Found it!");
//                break;
//            }
//        }
//        if (adjustedName == nil) {
//            adjustedName = name;
//            //NSLog(@"Did not find it.");
//        }
//    } else if (self.sdkRoot != nil) {
//        adjustedName = [self.sdkRoot stringByAppendingPathComponent:name];
//    } else {
//        adjustedName = name;
//    }
//
//    CDMachOFile *machOFile = _machOFilesByName[adjustedName];
//    if (machOFile == nil) {
//        CDFile *file = [CDFile fileWithContentsOfFile:adjustedName searchPathState:self.searchPathState];
//
//        if (file == nil || [self loadFile:file error:NULL] == NO)
//            NSLog(@"Warning: Failed to load: %@", adjustedName);
//
//        machOFile = _machOFilesByName[adjustedName];
//        if (machOFile == nil) {
//            NSLog(@"Warning: Couldn't load MachOFile with ID: %@, adjustedID: %@", name, adjustedName);
//        }
//    }
//
//    return machOFile;
//}

- (void)appendHeaderToString:(NSMutableString *)resultString;
{
    // Since this changes each version, for regression testing it'll be better to be able to not show it.
    if (self.shouldShowHeader == NO)
        return;

    [resultString appendString:@"//\n"];
    [resultString appendFormat:@"//     Generated by class-dump %s.\n", CLASS_DUMP_VERSION];
    [resultString appendString:@"//\n"];
    [resultString appendString:@"//  Copyright (C) 1997-2019 Steve Nygard.\n"];
    [resultString appendString:@"//\n\n"];

    if (self.sdkRoot != nil) {
        [resultString appendString:@"//\n"];
        [resultString appendFormat:@"// SDK Root: %@\n", self.sdkRoot];
        [resultString appendString:@"//\n\n"];
    }
}

- (void)registerTypes;
{
    for (CDObjectiveCProcessor *processor in self.objcProcessors) {
        [processor registerTypesWithObject:self.typeController phase:0];
    }
    [self.typeController endPhase:0];

    [self.typeController workSomeMagic];
}

- (void)showHeader;
{
//    if ([self.machOFiles count] > 0) {
//        [[[self.machOFiles lastObject] headerString:YES] print];
//    }
}

- (void)showLoadCommands;
{
//    if ([self.machOFiles count] > 0) {
//        [[[self.machOFiles lastObject] loadCommandString:YES] print];
//    }
}

@end
